/*!
 *
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License, version 2 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 *
 * Copyright (c) 2002-2018 Hitachi Vantara. All rights reserved.
 *
 */

package org.pentaho.platform.uifoundation.component.xml;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.pentaho.commons.connection.IPentahoResultSet;
import org.pentaho.metadata.model.Category;
import org.pentaho.metadata.model.Domain;
import org.pentaho.metadata.model.LogicalColumn;
import org.pentaho.metadata.model.LogicalModel;
import org.pentaho.metadata.model.concept.types.DataType;
import org.pentaho.metadata.model.concept.types.LocalizedString;
import org.pentaho.metadata.repository.IMetadataDomainRepository;
import org.pentaho.platform.api.engine.IParameterProvider;
import org.pentaho.platform.api.engine.IPentahoUrlFactory;
import org.pentaho.platform.api.engine.IRuntimeContext;
import org.pentaho.platform.engine.core.solution.SimpleParameterProvider;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.services.solution.SolutionHelper;
import org.pentaho.platform.uifoundation.messages.Messages;
import org.pentaho.platform.util.messages.LocaleHelper;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

@SuppressWarnings( "rawtypes" )
public class PMDUIComponent extends XmlComponent {

  private static final long serialVersionUID = -911457505257919399L;

  private static final Log logger = LogFactory.getLog( PMDUIComponent.class );

  public static final int ACTION_LIST_DOMAINS = 1;

  public static final int ACTION_LIST_MODELS = 2;

  public static final int ACTION_MODELS_DETAIL = 3;

  public static final int ACTION_LOAD_MODEL = 4;

  public static final int ACTION_LOOKUP = 5;

  private int action;

  private String domainName;

  private String modelId;

  private String columnId;

  private IParameterProvider parameters;

  public PMDUIComponent( final IPentahoUrlFactory urlFactory, final List messages ) {
    super( urlFactory, messages, "" ); //$NON-NLS-1$
  }

  @Override
  public Log getLogger() {
    return PMDUIComponent.logger;
  }

  @Override
  public boolean validate() {
    return true;
  }

  public IMetadataDomainRepository getMetadataRepository() {
    return PentahoSystem.get( IMetadataDomainRepository.class, getSession() );
  }

  @Override
  public Document getXmlContent() {

    if ( action == PMDUIComponent.ACTION_LIST_MODELS ) {
      return listModels();
    } else if ( action == PMDUIComponent.ACTION_LOAD_MODEL ) {
      return loadModel();
    } else if ( action == PMDUIComponent.ACTION_LOOKUP ) {
      return getLookup();
    } else {
      throw new RuntimeException( Messages.getInstance().getErrorString(
          "PMDUIComponent.ERROR_0002_ILLEGAL_ACTION", String.valueOf( action ) ) ); //$NON-NLS-1$
    }
  }

  private Document listModels() {

    // Create a document that describes the result
    Document doc = DocumentHelper.createDocument();

    Element root = doc.addElement( "metadata" ); //$NON-NLS-1$

    Element modelsNode = root.addElement( "models" ); //$NON-NLS-1$
    if ( domainName == null ) {
      try {
        for ( String domain : getMetadataRepository().getDomainIds() ) {
          addThinDomainModels( domain, modelsNode, root );
        }
      } catch ( Throwable t ) {
        error( Messages.getInstance().getString( "PMDUIComponent.ERROR_0001_GET_MODEL_LIST" ) ); //$NON-NLS-1$
        t.printStackTrace();
        root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_NO_DOMAIN_SPECIFIED" ) ); //$NON-NLS-1$ //$NON-NLS-2$
      }

    } else {
      addThinDomainModels( domainName, modelsNode, root );
    }
    return doc;
  }

  private void addThinDomainModels( final String domain, final Element modelsNode, final Element root ) {

    IMetadataDomainRepository repo = getMetadataRepository();

    Domain domainObject = repo.getDomain( domain );

    String locale = LocaleHelper.getClosestLocale( LocaleHelper.getLocale().toString(), domainObject.getLocaleCodes() );

    Element modelNode;
    for ( LogicalModel model : domainObject.getLogicalModels() ) {
      String vis = (String) model.getProperty( "visible" );
      if ( vis != null ) {
        String[] visibleContexts = vis.split( "," );
        boolean visibleToContext = false;
        for ( String context : visibleContexts ) {
          if ( "adhoc".equals( context.trim() ) ) {
            visibleToContext = true;
            break;
          }
        }
        if ( !visibleToContext ) {
          continue;
        }
      }
      modelNode = modelsNode.addElement( "model" ); //$NON-NLS-1$
      modelNode.addElement( "domain_id" ).setText( domain ); //$NON-NLS-1$
      if ( model.getId() != null ) {
        modelNode.addElement( "model_id" ).setText( model.getId() ); //$NON-NLS-1$
      }
      String modelName = model.getName( locale );
      if ( modelName != null ) {
        modelNode.addElement( "model_name" ).setText( modelName ); //$NON-NLS-1$
      }

      if ( model.getDescription() != null ) {
        String modelDescription = model.getDescription( locale );
        if ( modelDescription != null ) {
          modelNode.addElement( "model_description" ).setText( modelDescription ); //$NON-NLS-1$
        }
      }
    }
    return;
  }

  private Document loadModel() {
    // Create a document that describes the result
    Document doc = DocumentHelper.createDocument();

    Element root = doc.addElement( "metadata" ); //$NON-NLS-1$

    if ( domainName == null ) {
      // we can't do this without a model
      root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_NO_DOMAIN_SPECIFIED" ) ); //$NON-NLS-1$ //$NON-NLS-2$
      return doc;
    }

    if ( modelId == null ) {
      // we can't do this without a model
      root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_NO_MODEL_SPECIFIED" ) ); //$NON-NLS-1$ //$NON-NLS-2$
      return doc;
    }

    Element modelNode = root.addElement( "model" ); //$NON-NLS-1$

    // because it's lighter weight, check the thin model
    Domain domain = getMetadataRepository().getDomain( domainName );
    if ( domain == null ) {
      root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_DOMAIN_LOADING_ERROR", domainName ) ); //$NON-NLS-1$ //$NON-NLS-2$
      return doc;
    }
    String locale = LocaleHelper.getClosestLocale( LocaleHelper.getLocale().toString(), domain.getLocaleCodes() );
    LogicalModel model = domain.findLogicalModel( modelId );

    if ( model == null ) {
      root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_MODEL_LOADING_ERROR", modelId ) ); //$NON-NLS-1$ //$NON-NLS-2$
      error( Messages.getInstance().getString( "PMDUIComponent.USER_MODEL_LOADING_ERROR", modelId ) ); //$NON-NLS-1$
      return doc;
    }
    modelNode.addElement( "domain_id" ).setText( domainName ); //$NON-NLS-1$
    if ( model.getId() != null ) {
      modelNode.addElement( "model_id" ).setText( model.getId() ); //$NON-NLS-1$
    }
    if ( model.getName( locale ) != null ) {
      modelNode.addElement( "model_name" ).setText( model.getName( locale ) ); //$NON-NLS-1$
    }
    if ( model.getDescription( locale ) != null ) {
      modelNode.addElement( "model_description" ).setText( model.getDescription( locale ) ); //$NON-NLS-1$
    }

    Element tableNode;

    for ( Category category : model.getCategories() ) {
      tableNode = modelNode.addElement( "view" ); //$NON-NLS-1$
      if ( category.getId() != null ) {
        tableNode.addElement( "view_id" ).setText( category.getId() ); //$NON-NLS-1$
      }
      if ( category.getName( locale ) != null ) {
        tableNode.addElement( "view_name" ).setText( category.getName( locale ) ); //$NON-NLS-1$
      }
      if ( category.getDescription( locale ) != null ) {
        tableNode.addElement( "view_description" ).setText( category.getDescription( locale ) ); //$NON-NLS-1$
      }
      for ( LogicalColumn column : category.getLogicalColumns() ) {
        Boolean hidden = (Boolean) column.getProperty( "hidden" ); //$NON-NLS-1$
        if ( hidden != null && hidden ) {
          continue;
        }
        addColumn( column, tableNode, locale );
      }
    }

    return doc;
  }

  @SuppressWarnings( "deprecation" )
  public void addColumn( final LogicalColumn column, final Element tableNode, final String locale ) {
    Element columnNode = tableNode.addElement( "column" ); //$NON-NLS-1$

    if ( column.getId() != null ) {
      columnNode.addElement( "column_id" ).setText( column.getId() ); //$NON-NLS-1$
    }
    if ( column.getName( locale ) != null ) {
      columnNode.addElement( "column_name" ).setText( column.getName( locale ) ); //$NON-NLS-1$
    }
    if ( column.getDescription( locale ) != null ) {
      columnNode.addElement( "column_description" ).setText( column.getDescription( locale ) ); //$NON-NLS-1$
    }
    if ( column.getFieldType() != null ) {
      // TODO this should take a locale
      String desc = column.getFieldType().getDescription();
      desc = org.pentaho.pms.messages.Messages.getString( desc );
      columnNode.addElement( "column_field_type" ).setText( desc ); //$NON-NLS-1$
    }
    DataType dataType = column.getDataType();
    if ( dataType != null ) {
      columnNode.addElement( "column_type" ).setText( dataType.getName() ); //$NON-NLS-1$
    }
    if ( column.getProperty( "lookup" ) != null ) { //$NON-NLS-1$
      columnNode.addElement( "column_lookup" ).setText( "true" ); //$NON-NLS-1$ //$NON-NLS-2$
    }
  }

  public Document getLookup() {
    // Create a document that describes the result
    Document doc = DocumentHelper.createDocument();

    Element root = doc.addElement( "metadata" ); //$NON-NLS-1$

    if ( domainName == null ) {
      // we can't do this without a model
      root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_NO_DOMAIN_SPECIFIED" ) ); //$NON-NLS-1$ //$NON-NLS-2$
      return doc;
    }

    if ( modelId == null ) {
      // we can't do this without a view
      root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_NO_MODEL_SPECIFIED" ) ); //$NON-NLS-1$ //$NON-NLS-2$
      return doc;
    }

    if ( columnId == null ) {
      // we can't do this without a view
      root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_NO_COLUMN_SPECIFIED" ) ); //$NON-NLS-1$ //$NON-NLS-2$
      return doc;
    }

    Domain domain = getMetadataRepository().getDomain( domainName );
    String locale = LocaleHelper.getClosestLocale( LocaleHelper.getLocale().toString(), domain.getLocaleCodes() );
    LogicalModel model = domain.findLogicalModel( modelId ); // This is the business view that was selected.
    if ( model == null ) {
      root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_MODEL_LOADING_ERROR", modelId ) ); //$NON-NLS-1$ //$NON-NLS-2$
      return doc;
    }

    LogicalColumn column = model.findLogicalColumn( columnId );
    if ( column == null ) {
      root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_COLUMN_NOT_FOUND" ) ); //$NON-NLS-1$ //$NON-NLS-2$
      return doc;
    }

    // Temporary hack to get the BusinessCategory. When fixed properly, you should be able to interrogate the
    // business column thingie for it's containing BusinessCategory.
    Category view = null;
    for ( Category category : model.getCategories() ) {
      for ( LogicalColumn col : category.getLogicalColumns() ) {
        if ( col.getId().equals( column.getId() ) ) {
          view = category;
          break;
        }
      }
    }

    if ( view == null ) {
      root.addElement( "message" ).setText( Messages.getInstance().getString( "PMDUIComponent.USER_VIEW_NOT_FOUND" ) ); //$NON-NLS-1$ //$NON-NLS-2$
      return doc;
    }

    String mql =
        "<mql><domain_type>relational</domain_type><domain_id>" + domainName + "</domain_id><model_id>" + modelId + "</model_id>"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    if ( column.getProperty( "lookup" ) == null ) { //$NON-NLS-1$
      mql += "<selection><view>" + view.getId() + "</view><column>" + column.getId() + "</column></selection>"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      mql +=
          "<orders><order><direction>asc</direction><view_id>" + view.getId() + "</view_id><column_id>" + column.getId() + "</column_id></order></orders>"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

    } else {

      String lookup = (String) column.getProperty( "lookup" ); //$NON-NLS-1$
      // assume model and view are the same...
      StringTokenizer tokenizer1 = new StringTokenizer( lookup, ";" ); //$NON-NLS-1$
      while ( tokenizer1.hasMoreTokens() ) {
        StringTokenizer tokenizer2 = new StringTokenizer( tokenizer1.nextToken(), "." ); //$NON-NLS-1$
        if ( tokenizer2.countTokens() == 2 ) {
          String lookupViewId = tokenizer2.nextToken();
          String lookupColumnId = tokenizer2.nextToken();
          mql += "<selection><view>" + lookupViewId + "</view><column>" + lookupColumnId + "</column></selection>"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }
      }
    }
    mql += "</mql>"; //$NON-NLS-1$
    ArrayList messages = new ArrayList();
    SimpleParameterProvider lookupParameters = new SimpleParameterProvider();
    lookupParameters.setParameter( "mql", mql ); //$NON-NLS-1$

    IRuntimeContext runtime =
        SolutionHelper.doAction(
            "/system/metadata/PickList.xaction", "lookup-list", lookupParameters, getSession(), messages, this ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
    IPentahoResultSet results = null;
    if ( runtime != null ) {
      if ( runtime.getStatus() == IRuntimeContext.RUNTIME_STATUS_SUCCESS ) {
        if ( runtime.getOutputNames().contains( "data" ) ) { //$NON-NLS-1$
          results = runtime.getOutputParameter( "data" ).getValueAsResultSet(); //$NON-NLS-1$
          Object[][] columnHeaders = results.getMetaData().getColumnHeaders();
          boolean hasColumnHeaders = columnHeaders != null;

          Element rowElement;
          Element dataElement = root.addElement( "data" ); //$NON-NLS-1$
          if ( hasColumnHeaders ) {
            for ( int rowNo = 0; rowNo < columnHeaders.length; rowNo++ ) {
              rowElement = dataElement.addElement( "COLUMN-HDR-ROW" ); //$NON-NLS-1$
              for ( int columnNo = 0; columnNo < columnHeaders[rowNo].length; columnNo++ ) {
                Object nameAttr = results.getMetaData().getAttribute( rowNo, columnNo, "name" ); //$NON-NLS-1$
                if ( ( nameAttr != null ) && ( nameAttr instanceof LocalizedString ) ) {
                  LocalizedString str = (LocalizedString) nameAttr;
                  String name = str.getLocalizedString( locale );
                  if ( name != null ) {
                    rowElement.addElement( "COLUMN-HDR-ITEM" ).setText( name ); //$NON-NLS-1$
                  } else {
                    rowElement.addElement( "COLUMN-HDR-ITEM" ).setText( columnHeaders[rowNo][columnNo].toString() ); //$NON-NLS-1$
                  }
                } else {
                  rowElement.addElement( "COLUMN-HDR-ITEM" ).setText( columnHeaders[rowNo][columnNo].toString() ); //$NON-NLS-1$
                }
              }
            }
          }
          Object[] row = results.next();
          while ( row != null ) {
            rowElement = dataElement.addElement( "DATA-ROW" ); //$NON-NLS-1$
            for ( Object element : row ) {
              if ( element == null ) {
                rowElement.addElement( "DATA-ITEM" ).setText( "" ); //$NON-NLS-1$ //$NON-NLS-2$
              } else {
                rowElement.addElement( "DATA-ITEM" ).setText( element.toString() ); //$NON-NLS-1$
              }
            }
            row = results.next();
          }
        }
      }
    }

    return doc;
  }

  public void setAction( final int action ) {
    this.action = action;
  }

  public int getAction() {
    return action;
  }

  public void setDomainName( final String domainName ) {
    this.domainName = domainName;
  }

  public String getDomainName() {
    return domainName;
  }

  public IParameterProvider getParameters() {
    return parameters;
  }

  public void setParameters( final IParameterProvider parameters ) {
    this.parameters = parameters;
  }

  public String getModelId() {
    return modelId;
  }

  public void setModelId( final String modelId ) {
    this.modelId = modelId;
  }

  public String getColumnId() {
    return columnId;
  }

  public void setColumnId( final String columnId ) {
    this.columnId = columnId;
  }

}
